#include "grflib_stdafx.h"
#include "../include/Texture.h"
#include <Logger.h>
#include "../include/D3D10Engine.h"
#include "../include/ImgResampler.h"
#include <BMPImg.h>

namespace grflib { namespace engine { namespace core {

/////////////////////////////////////////////////
// Generic Texture
CTexture::CTexture()
    : m_pTexture(0),
    m_pSRView(0),
    m_pRenderTargetView(0),
    m_bindTarget(TextureSpec::BT_NONE),
    m_usage(TextureSpec::TU_GPU_READ)
{
    memset(m_dimension, 0, sizeof(std::size_t) * 3);
}

CTexture::CTexture(TextureSpec::TextureUsage usage,
    TextureSpec::TextureDimension dim,
    TextureSpec::BindTarget bindTarget,
    bool bInitialized,
    DXGI_FORMAT format)
    : m_pTexture(0),
    m_pSRView(0),
    m_pRenderTargetView(0),
    m_pDepthStencilView(0),
    m_dimSpec(dim),
    m_bindTarget(bindTarget),
    m_usage(usage),
    m_bInitialized(bInitialized),
    m_bAutoMipmap(false),
    m_format(format)
{
    memset(m_dimension, 0, sizeof(std::size_t) * 3);
}


CTexture::~CTexture()
{
    OnDestroy();
}

void CTexture::OnDestroy()
{
    DestroyResource();
}

std::size_t CTexture::GetDimension(std::size_t dim)
{
    if (dim < 3)
    {
        return m_dimension[dim];
    }
    return 0;
}

void CTexture::DestroyResource()
{
    SAFE_RELEASE(m_pSRView);
    SAFE_RELEASE(m_pDepthStencilView);
    SAFE_RELEASE(m_pRenderTargetView);
}

/////////////////////////////////////////////////
// 2D Texture
CTexture2D::CTexture2D(
    UINT usage,
    TextureSpec::BindTarget bindTarget,
    std::size_t w,
    std::size_t h,
    DXGI_FORMAT format)
    : CTexture((TextureSpec::TextureUsage)usage, TextureSpec::TD_2D, bindTarget, false, format),
    m_nChannel(0)
{
    m_dimension[0] = w;
    m_dimension[1] = h;
}

CTexture2D::CTexture2D(
    UINT usage,
    TextureSpec::BindTarget bindTarget,
    w32lib::ImagePtr imgPtr)
    : CTexture((TextureSpec::TextureUsage)usage, TextureSpec::TD_2D, bindTarget, true),
    m_nChannel(0)
{
    m_pMemImg = imgPtr;
    m_dimension[0] = m_pMemImg->GetWidth();
    m_dimension[1] = m_pMemImg->GetHeight();
}

CTexture2D::~CTexture2D()
{
    OnDestroy();
    m_pMemImg.Delete();
}

HRESULT CTexture2D::OnCreateDevice(ID3D10Device *pd3dDevice, 
    const DXGI_SURFACE_DESC *pBufferSurfaceDesc)
{
    if (m_bInitialized && 0 == m_pMemImg.Get())
        return E_FAIL;
    HRESULT hr;

    DXGI_FORMAT format;
    //get texture format
    if (m_bInitialized)
    {
        m_dimension[0] = m_pMemImg->GetWidth();
        m_dimension[1] = m_pMemImg->GetHeight();
        format = texture_format::Mapping(m_pMemImg);
    }
    else
    {
        format = m_format;
    }

    if (0 == m_dimension[0] || 0 == m_dimension[1] || 0 == m_usage)
        return E_INVALIDARG;

    if (DXGI_FORMAT_UNKNOWN == format)
        return E_NOTIMPL;

    UINT bpc;
    bool bIsFloat;
    texture_format::Mapping(format, bpc, m_nChannel, bIsFloat);

    D3D10_USAGE usage = D3D10_USAGE_DEFAULT;
    UINT bindFlag = 0, cpuAccess = 0;
    texture_format::Mapping(usage, bindFlag, cpuAccess, m_usage, m_bindTarget);

    if (!m_bInitialized && (usage == D3D10_USAGE_IMMUTABLE)) //immutable texture must be initialized
        return E_INVALIDARG;

    ID3D10Texture2D *pTexture2D = 0;

    //mipmap level
    UINT nLevel = (UINT)(log((float)min(m_dimension[0], m_dimension[1])) / log(2.0f) + 1.0f);

    //set texture format
    D3D10_TEXTURE2D_DESC texDesc;
    memset( &texDesc, 0, sizeof(D3D10_TEXTURE2D_DESC) );
    texDesc.Width = m_dimension[0];
    texDesc.Height = m_dimension[1];
    texDesc.MipLevels = m_bAutoMipmap ? nLevel : 1;
    texDesc.ArraySize = 1;
    texDesc.Format = format;
    texDesc.SampleDesc.Count = 1;
    texDesc.Usage = usage;
    texDesc.BindFlags = bindFlag;
    texDesc.CPUAccessFlags = cpuAccess;


    //create texture
    if (m_bInitialized) //initialized with an input image
    {
        if (m_bAutoMipmap) //allocate memory for auto generated mipmaps
        {
            //create descriptors
            D3D10_SUBRESOURCE_DATA *pInitData = new D3D10_SUBRESOURCE_DATA[nLevel];
            if (!pInitData)
            {
                W32ERR(_T("Failed to allocate memory for auto-generated mipmap descriptors!"));
                return E_FAIL;
            }

            //set the original image as the most detailed mip level
            pInitData[0].pSysMem = m_pMemImg->GetBuffer();
            pInitData[0].SysMemPitch = m_pMemImg->BytesPerLine();

            //create and set other mip levels
            UINT levelWidth = m_dimension[0] >> 1, levelHeight = m_dimension[1] >> 1;
            UINT curMipLevel = 1;
            while (levelWidth >= 1 && levelHeight >= 1)
            {
                w32lib::ImagePtr imgPtr;
                if (!imgPtr.New(m_pMemImg->m_bpc))
                {
                    W32ERR(_T("Failed to allocate memory for auto-generated mipmaps!"));
                    return E_FAIL;
                }
                imgPtr->SetSize(levelWidth, levelHeight, m_pMemImg->GetBPP() / m_pMemImg->GetBPC());
                image::DownSampleLinear(imgPtr, m_pMemImg);

                pInitData[curMipLevel].pSysMem = imgPtr->GetBuffer();
                pInitData[curMipLevel].SysMemPitch = imgPtr->BytesPerLine();
                
                //DEBUG
                //TCHAR fnfn[MAX_PATH];
                //_stprintf_s(fnfn, _T("e:/lod%02d.bmp"), m_pMipmaps.size());
                //w32lib::CBMPImg::WriteToFile(fnfn, (unsigned char*)imgPtr->GetBuffer(), levelWidth, levelHeight, imgPtr->GetBPP());
                //

                m_pMipmaps.push_back(imgPtr);
                levelWidth >>= 1;
                levelHeight >>= 1;
                curMipLevel++;
            }

            V_RETURN( pd3dDevice->CreateTexture2D(&texDesc, pInitData, &pTexture2D) );
            _SafeDeleteArray(pInitData);
        }
        else
        {
            D3D10_SUBRESOURCE_DATA initData;
            initData.pSysMem = m_pMemImg->GetBuffer();
            initData.SysMemPitch = m_pMemImg->BytesPerLine();
            V_RETURN( pd3dDevice->CreateTexture2D(&texDesc, &initData, &pTexture2D) );
        }
    }
    else //not initialized
    {
        V_RETURN( pd3dDevice->CreateTexture2D(&texDesc, 0, &pTexture2D) );

        if (usage & D3D10_USAGE_STAGING) //memory is only allocated if it's staging texture
        {
            UINT bpc, channel;
            bool isFloat;
            m_pMemImg->Delete();
            if (texture_format::Mapping(format, bpc, channel, isFloat))
            {
                if (m_pMemImg->New(w32lib::ImagePtr::Mapping(bpc, isFloat)))
                    m_pMemImg->SetSize(m_dimension[0], m_dimension[1], channel);
            }
        }
    }
    m_pTexture = pTexture2D;

    if (bindFlag & D3D10_BIND_SHADER_RESOURCE)
    {
        D3D10_SHADER_RESOURCE_VIEW_DESC shaderResrcDesc;
        memset(&shaderResrcDesc, 0, sizeof(D3D10_SHADER_RESOURCE_VIEW_DESC));
        shaderResrcDesc.Format = texDesc.Format;
        shaderResrcDesc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2D;
        shaderResrcDesc.Texture2D.MostDetailedMip = 0;
        if (m_bAutoMipmap)
            shaderResrcDesc.Texture2D.MipLevels = m_pMipmaps.size() + 1;
        else
            shaderResrcDesc.Texture2D.MipLevels = 1;
        V_RETURN( pd3dDevice->CreateShaderResourceView(pTexture2D, &shaderResrcDesc, &m_pSRView) );
    }
    if (bindFlag & D3D10_BIND_RENDER_TARGET)
    {
        D3D10_RENDER_TARGET_VIEW_DESC rtDesc;
        memset(&rtDesc, 0, sizeof(D3D10_RENDER_TARGET_VIEW_DESC));
        rtDesc.Format = texDesc.Format;
        rtDesc.ViewDimension = D3D10_RTV_DIMENSION_TEXTURE2D;
        rtDesc.Texture2D.MipSlice = 0;
        V_RETURN( pd3dDevice->CreateRenderTargetView(pTexture2D, &rtDesc, &m_pRenderTargetView) );
    }
    if (bindFlag & D3D10_BIND_DEPTH_STENCIL)
    {
        D3D10_DEPTH_STENCIL_VIEW_DESC dsDesc;
        memset(&dsDesc, 0, sizeof(D3D10_DEPTH_STENCIL_VIEW_DESC));
        dsDesc.Format = texDesc.Format;
        dsDesc.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2D;
        dsDesc.Texture2D.MipSlice = 0;
        V_RETURN( pd3dDevice->CreateDepthStencilView(pTexture2D, &dsDesc, &m_pDepthStencilView) );
    }

    return S_OK;
}

HRESULT CTexture2D::CopyFrom(boost::shared_ptr<CTexture> &pSrc)
{
    boost::shared_ptr<CTexture2D> pSrc2D = boost::dynamic_pointer_cast<CTexture2D>(pSrc);
    if (!pSrc2D)
    {
        W32ERR(_T("Texture dimensions don't match!"));
        return E_INVALIDARG;
    }

    HRESULT hr;
    if (pSrc2D->m_dimension[0] != m_dimension[0] 
        || pSrc2D->m_dimension[1] != m_dimension[1] 
        || pSrc2D->m_format != m_format
        || pSrc2D->m_bAutoMipmap != m_bAutoMipmap)
    {
        m_bAutoMipmap = pSrc2D->m_bAutoMipmap;
        V_RETURN( Resize(pSrc2D->m_dimension[0], pSrc2D->m_dimension[1], pSrc2D->m_format) );
    }
    CEngine::Instance().GetD3DDevice()->CopyResource(m_pTexture, pSrc2D->m_pTexture); //async
    return S_OK;
}

HRESULT CTexture2D::ReadBack(UINT mipLevel)
{
    D3D10_USAGE usage = D3D10_USAGE_DEFAULT;
    UINT bindFlag = 0, cpuAccess = 0;
    texture_format::Mapping(usage, bindFlag, cpuAccess, m_usage, m_bindTarget);
    if (0 == (usage & D3D10_USAGE_STAGING)) //not staging, readback not allowed!
    {
        W32ERR(_T("Reading a non-staging texture!"));
        return E_ACCESSDENIED;
    }

    HRESULT hr;
    ID3D10Texture2D *pTex2D = static_cast<ID3D10Texture2D *>(m_pTexture);
    D3D10_MAPPED_TEXTURE2D mapped;

    UINT subresourceId = D3D10CalcSubresource(mipLevel, 0, 1);
    V_RETURN( pTex2D->Map(subresourceId, D3D10_MAP_READ, 0, &mapped) );
    if (mipLevel == 0)
    {
        for (size_t y = 0; y < m_dimension[1]; y++)
            memcpy((char*)m_pMemImg->GetBuffer() + y * m_pMemImg->BytesPerLine(), (char *)mapped.pData + y * mapped.RowPitch, mapped.RowPitch);
    }
    else
    {
        UINT bpl = m_pMipmaps[mipLevel - 1]->BytesPerLine();
        UINT levelHeight = m_pMipmaps[mipLevel - 1]->GetHeight();
        for (size_t y = 0; y < levelHeight; y++)
            memcpy((char*)m_pMipmaps[mipLevel - 1]->GetBuffer() + y * bpl, (char *)mapped.pData + y * mapped.RowPitch, mapped.RowPitch);
    }
    pTex2D->Unmap(0);
    return S_OK;
}

HRESULT CTexture2D::UpdateGPU()
{
	if (m_usage != (TextureSpec::TextureUsage)(TextureSpec::TU_GPU_READ | TextureSpec::TU_CPU_WRITE))
	{
		W32ERR(_T("Only dynamic textures can be updated!"));
		return E_FAIL;
	}

	HRESULT hr;
    ID3D10Texture2D *pTex2D = static_cast<ID3D10Texture2D *>(m_pTexture);
    D3D10_MAPPED_TEXTURE2D mapped;
    V_RETURN( pTex2D->Map(0, D3D10_MAP_WRITE_DISCARD, 0, &mapped) );
	
	for (size_t y = 0; y < m_dimension[1]; y++)
		memcpy((char *)mapped.pData + y * mapped.RowPitch, (char*)m_pMemImg->GetBuffer() + y * m_pMemImg->BytesPerLine(), mapped.RowPitch);

	pTex2D->Unmap(0);
	return S_OK;
}

HRESULT CTexture2D::Resize(std::size_t width, std::size_t height, DXGI_FORMAT format)
{
    if (width == 0 || height == 0)
        return E_INVALIDARG;

    HRESULT hr;
    std::size_t bpc, channel;
    bool isFloat;

    if (!texture_format::Mapping(format, bpc, channel, isFloat))
    {
        W32ERR(_T("2D texture format not recognized!"));
        return E_FAIL;
    }

    if (m_pMemImg->Get())
    {
        w32lib::ImageBytePerChannel bpcenum = w32lib::ImagePtr::Mapping(bpc, isFloat);

        m_pMemImg->Delete();
        if (m_pMemImg->New(bpcenum) && m_pMemImg->SetSize(width, height, channel))
        {
            OnDestroy();
            m_bInitialized = true;
            V_RETURN(OnCreateDevice(CEngine::Instance().GetD3DDevice(), 0)); //assuming OnCreateDevice() doesn't use DXGI_SURFACE_DESC
            return S_OK;
        }
    }
    else
    {
        OnDestroy();
        m_bInitialized = false;
        m_dimension[0] = width; m_dimension[1] = height;
        m_format = format;
        V_RETURN(OnCreateDevice(CEngine::Instance().GetD3DDevice(), 0)); //assuming OnCreateDevice() doesn't use DXGI_SURFACE_DESC
    }

    W32ERR(_T("Failed to resize 2D texture!"));
    return E_FAIL;

}

void CTexture2D::OnDestroy() //virtual
{
    DestroyResource2D();
    DestroyResource();
}

void CTexture2D::DestroyResource2D()
{
    SAFE_RELEASE(m_pTexture);
    for (std::vector<w32lib::ImagePtr>::iterator iter = m_pMipmaps.begin();
        iter != m_pMipmaps.end();
        ++iter)
    {
        iter->Delete();
    }
    m_pMipmaps.clear();
}


/////////////////////////////////////////////////
// 1D Texture
CTexture1D::CTexture1D(UINT usage, TextureSpec::BindTarget bindTarget, std::size_t width, DXGI_FORMAT format)
    : CTexture((TextureSpec::TextureUsage)usage, TextureSpec::TD_1D, bindTarget, false, format)
{
    m_dimension[0] = width;
}

CTexture1D::CTexture1D(UINT usage, TextureSpec::BindTarget bindTarget, w32lib::ImagePtr imgPtr)
    : CTexture((TextureSpec::TextureUsage)usage, 
                TextureSpec::TD_1D, 
                bindTarget,
                true)
{
    m_pMemImg = imgPtr;
    m_dimension[0] = m_pMemImg->GetWidth();
}

CTexture1D::~CTexture1D()
{
    OnDestroy();
    m_pMemImg.Delete();
}

void CTexture1D::OnDestroy() //virtual
{
    DestroyResource1D();
    DestroyResource();
}

void CTexture1D::DestroyResource1D()
{
    SAFE_RELEASE(m_pTexture);
    for (std::vector<w32lib::ImagePtr>::iterator iter = m_pMipmaps.begin();
         iter != m_pMipmaps.end();
         ++iter)
    {
        iter->Delete();
    }
    m_pMipmaps.clear();
}

HRESULT CTexture1D::OnCreateDevice(ID3D10Device *pd3dDevice, 
    const DXGI_SURFACE_DESC *pBufferSurfaceDesc)
{
    if (m_bInitialized && 0 == m_pMemImg.Get())
        return E_FAIL;
    HRESULT hr;

    DXGI_FORMAT format;
    //get texture format
    if (m_bInitialized)
    {
        m_dimension[0] = m_pMemImg->GetWidth();
        format = texture_format::Mapping(m_pMemImg);
    }
    else
    {
        format = m_format;
    }

    if (0 == m_dimension[0])
        return S_OK;

    if (DXGI_FORMAT_UNKNOWN == format)
        return E_NOTIMPL;

    if (0 == m_dimension[0] || 0 == m_usage)
        return E_INVALIDARG;

    D3D10_USAGE usage = D3D10_USAGE_DEFAULT;
    UINT bindFlag = 0, cpuAccess = 0;
    texture_format::Mapping(usage, bindFlag, cpuAccess, m_usage, m_bindTarget);

    if (!m_bInitialized && (usage == D3D10_USAGE_IMMUTABLE)) //immutable texture must be initialized
        return E_INVALIDARG;

    ID3D10Texture1D *pTexture1D = 0;

    //mipmap level
    UINT nLevel = (UINT)(log((float)m_dimension[0]) / log(2.0f) + 1.0f);

    //set texture format
    D3D10_TEXTURE1D_DESC texDesc;
    memset( &texDesc, 0, sizeof(D3D10_TEXTURE1D_DESC) );
    texDesc.Width = m_dimension[0];
    texDesc.MipLevels = m_bAutoMipmap ? nLevel : 1;
    texDesc.ArraySize = 1;
    texDesc.Format = format;
    texDesc.Usage = usage;
    texDesc.BindFlags = bindFlag;
    texDesc.CPUAccessFlags = cpuAccess;
    texDesc.MiscFlags = 0;

    //create texture
    if (m_bInitialized)
    {
        if (m_bAutoMipmap)
        {
            //create descriptors
            D3D10_SUBRESOURCE_DATA *pInitData = new D3D10_SUBRESOURCE_DATA[nLevel];
            if (!pInitData)
            {
                W32ERR(_T("Failed to allocate memory for auto-generated mipmap descriptors!"));
                return E_FAIL;
            }

            //set the original image as the most detailed mip level
            pInitData[0].pSysMem = m_pMemImg->GetBuffer();
            pInitData[0].SysMemPitch = m_pMemImg->BytesPerLine();

            //create and set other mip levels
            UINT levelWidth = m_dimension[0] >> 1;
            UINT curMipLevel = 1;
            while (levelWidth >= 1)
            {
                w32lib::ImagePtr imgPtr;
                if (!imgPtr.New(m_pMemImg->m_bpc))
                {
                    W32ERR(_T("Failed to allocate memory for auto-generated mipmaps!"));
                    return E_FAIL;
                }
                imgPtr->SetSize(levelWidth, 1, m_pMemImg->GetBPP() / m_pMemImg->GetBPC());
                image::DownSampleLinear(imgPtr, m_pMemImg);

                pInitData[curMipLevel].pSysMem = imgPtr->GetBuffer();
                pInitData[curMipLevel].SysMemPitch = imgPtr->BytesPerLine();//(levelWidth, imgPtr->GetBPP() / imgPtr->GetBPC());

                m_pMipmaps.push_back(imgPtr);
                levelWidth >>= 1;
                curMipLevel++;
            }

            V_RETURN( pd3dDevice->CreateTexture1D(&texDesc, pInitData, &pTexture1D) );
            _SafeDeleteArray(pInitData);
        }
        else
        {
            D3D10_SUBRESOURCE_DATA initData;
            initData.pSysMem = m_pMemImg->GetBuffer();
            initData.SysMemPitch = m_pMemImg->BytesPerLine();
            V_RETURN( pd3dDevice->CreateTexture1D(&texDesc, &initData, &pTexture1D) );
        }
    }
    else
    {
        V_RETURN( pd3dDevice->CreateTexture1D(&texDesc, 0, &pTexture1D) );
        if (usage & D3D10_USAGE_STAGING)
        {
            UINT bpc, channel;
            bool isFloat;
            m_pMemImg->Delete();
            if (texture_format::Mapping(format, bpc, channel, isFloat)) //format recognized
            {
                if (m_pMemImg->New(w32lib::ImagePtr::Mapping(bpc, isFloat)))
                    m_pMemImg->SetSize(m_dimension[0], 1, channel);
            }
        }
    }
    m_pTexture = pTexture1D;

    if (bindFlag & D3D10_BIND_SHADER_RESOURCE)
    {
        D3D10_SHADER_RESOURCE_VIEW_DESC shaderResrcDesc;
        memset(&shaderResrcDesc, 0, sizeof(D3D10_SHADER_RESOURCE_VIEW_DESC));
        shaderResrcDesc.Format = texDesc.Format;
        shaderResrcDesc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE1D;
        shaderResrcDesc.Texture1D.MostDetailedMip = 0;
        if (m_bAutoMipmap)
            shaderResrcDesc.Texture1D.MipLevels = m_pMipmaps.size() + 1;
        else
            shaderResrcDesc.Texture1D.MipLevels = 1;
        V_RETURN( pd3dDevice->CreateShaderResourceView(pTexture1D, &shaderResrcDesc, &m_pSRView) );
    }
    if (bindFlag & D3D10_BIND_RENDER_TARGET)
    {
        D3D10_RENDER_TARGET_VIEW_DESC rtDesc;
        memset(&rtDesc, 0, sizeof(D3D10_RENDER_TARGET_VIEW_DESC));
        rtDesc.Format = texDesc.Format;
        rtDesc.ViewDimension = D3D10_RTV_DIMENSION_TEXTURE1D;
        rtDesc.Texture1D.MipSlice = 0;
        V_RETURN( pd3dDevice->CreateRenderTargetView(pTexture1D, &rtDesc, &m_pRenderTargetView) );
    }
    if (bindFlag & D3D10_BIND_DEPTH_STENCIL)
    {
        D3D10_DEPTH_STENCIL_VIEW_DESC dsDesc;
        memset(&dsDesc, 0, sizeof(D3D10_DEPTH_STENCIL_VIEW_DESC));
        dsDesc.Format = texDesc.Format;
        dsDesc.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE1D;
        dsDesc.Texture1D.MipSlice = 0;
        V_RETURN( pd3dDevice->CreateDepthStencilView(pTexture1D, &dsDesc, &m_pDepthStencilView) );
    }

    return S_OK;
}

HRESULT CTexture1D::CopyFrom(boost::shared_ptr<CTexture> &pSrc)
{
    boost::shared_ptr<CTexture1D> pSrc1D = boost::dynamic_pointer_cast<CTexture1D>(pSrc);
    if (!pSrc1D)
    {
        W32ERR(_T("Texture dimensions don't match!"));
        return E_INVALIDARG;
    }

    HRESULT hr;
    if (pSrc1D->m_dimension[0] != m_dimension[0] 
        || pSrc1D->m_format != m_format
        || pSrc1D->m_bAutoMipmap != m_bAutoMipmap)
    {
        m_bAutoMipmap = pSrc1D->m_bAutoMipmap;
        V_RETURN( Resize(pSrc1D->m_dimension[0], pSrc1D->m_format) );
    }
    CEngine::Instance().GetD3DDevice()->CopyResource(m_pTexture, pSrc1D->m_pTexture); //async
    return S_OK;
}

HRESULT CTexture1D::Resize(std::size_t width, DXGI_FORMAT format)
{
    if (width == 0)
        return E_INVALIDARG;

    HRESULT hr;
    std::size_t bpc, channel;
    bool isFloat;

    if (!texture_format::Mapping(format, bpc, channel, isFloat))
    {
        W32ERR(_T("1D texture format not recognized!"));
        return E_FAIL;
    }

    w32lib::ImageBytePerChannel bpcenum = w32lib::ImagePtr::Mapping(bpc, isFloat);
    
    m_pMemImg->Delete();
    if (m_pMemImg->New(bpcenum))
    {
        if (m_pMemImg->SetSize(width, 1, channel))
        {
            OnDestroy();
            m_bInitialized = true;
            V_RETURN(OnCreateDevice(DXUTGetD3D10Device(), 0)); //assuming OnCreateDevice() doesn't use DXGI_SURFACE_DESC
            return S_OK;
        }
    }

    W32ERR(_T("Failed to resize buffer for 2D texture!"));
    return E_FAIL;	
}

HRESULT CTexture1D::UpdateGPU()
{
	if (m_usage != (TextureSpec::TextureUsage)(TextureSpec::TU_GPU_READ | TextureSpec::TU_CPU_WRITE))
	{
		W32ERR(_T("Only dynamic textures can be updated!"));
		return E_FAIL;
	}

	HRESULT hr;
    ID3D10Texture1D *pTex1D = static_cast<ID3D10Texture1D *>(m_pTexture);
    void *mapped = 0;
    V_RETURN( pTex1D->Map(0, D3D10_MAP_WRITE_DISCARD, 0, &mapped) );
	
    memcpy((char *)mapped, (char*)m_pMemImg->GetBuffer(), m_pMemImg->BytesPerLine());

	pTex1D->Unmap(0);
	return S_OK;
}

/////////////////////////////////////////////////
// 3D Volume Texture
CTexture3D::CTexture3D(UINT usage, TextureSpec::BindTarget bindTarget,
                       std::size_t dim1, std::size_t dim2, std::size_t dim3,
                       DXGI_FORMAT format)
                       : CTexture((TextureSpec::TextureUsage)usage, 
                       TextureSpec::TD_3D, 
                       bindTarget,
                       false,
                       format),
                       m_releaseMem(true)
{
    m_dimension[0] = dim1;
    m_dimension[1] = dim2;
    m_dimension[2] = dim3;
}

CTexture3D::CTexture3D(UINT usage,
                       TextureSpec::BindTarget bindTarget,
                       boost::shared_array<char> &initData,
                       std::size_t dim1, 
                       std::size_t dim2, 
                       std::size_t dim3,
                       DXGI_FORMAT format)
                       :  CTexture((TextureSpec::TextureUsage)usage, TextureSpec::TD_3D, bindTarget, true),
                       m_format(format),
                       m_data(initData),
                       m_releaseMem(true)
{
    m_dimension[0] = dim1;
    m_dimension[1] = dim2;
    m_dimension[2] = dim3;
    texture_format::Mapping(m_format, m_bpc, m_nChannel, m_isFloat);
}

CTexture3D::CTexture3D(UINT usage,
                       TextureSpec::BindTarget bindTarget,
                       w32lib::ImagePtr imgPtr,
                       std::size_t dim2)
                       : CTexture((TextureSpec::TextureUsage)usage,
                       TextureSpec::TD_3D,
                       bindTarget,
                       true),
                       m_data((char*)imgPtr->GetBuffer()),
                       m_releaseMem(false)
{
    m_dimension[0] = imgPtr->GetWidth();
    m_dimension[1] = dim2;
    m_dimension[2] = imgPtr->GetHeight() / dim2;
    texture_format::Mapping(m_format, m_bpc, m_nChannel, m_isFloat);
}

CTexture3D::~CTexture3D()
{
    OnDestroy();
    if (m_releaseMem) m_data.reset();
}

HRESULT CTexture3D::OnCreateDevice(ID3D10Device *pd3dDevice, 
    const DXGI_SURFACE_DESC *pBufferSurfaceDesc)
{
    if (m_bInitialized && !m_data.get())
        return E_FAIL;
    HRESULT hr;

    //get texture format
    if (0 == m_dimension[0] || 0 == m_dimension[1] || 0 == m_dimension[2])
        return E_INVALIDARG;

    DXGI_FORMAT format = m_format;
    if (DXGI_FORMAT_UNKNOWN == format)
        return E_NOTIMPL;

    if (0 == m_usage)
        return E_INVALIDARG;

    D3D10_USAGE usage = D3D10_USAGE_DEFAULT;
    UINT bindFlag = 0, cpuAccess = 0;
    texture_format::Mapping(usage, bindFlag, cpuAccess, m_usage, m_bindTarget);

    if (!m_bInitialized && (usage == D3D10_USAGE_IMMUTABLE)) //immutable texture must be initialized
        return E_INVALIDARG;

    ID3D10Texture3D *pTexture3D = 0;

    //set texture format
    D3D10_TEXTURE3D_DESC texDesc;
    memset( &texDesc, 0, sizeof(D3D10_TEXTURE3D_DESC) );
    texDesc.Width = m_dimension[0];
    texDesc.Height = m_dimension[1];
    texDesc.Depth = m_dimension[2];
    texDesc.MipLevels = m_bAutoMipmap ? 0 : 1;
    texDesc.Format = format;
    texDesc.MiscFlags = 0;
    texDesc.Usage = usage;
    texDesc.BindFlags = bindFlag;
    texDesc.CPUAccessFlags = cpuAccess;

    //create texture
    if (m_bInitialized)
    {
        D3D10_SUBRESOURCE_DATA initData;
        initData.pSysMem = m_data.get();
        initData.SysMemPitch = ((m_dimension[0] * m_nChannel * m_bpc / 8 + 3) >> 2) << 2;
        initData.SysMemSlicePitch = initData.SysMemPitch * m_dimension[1];
        V_RETURN( pd3dDevice->CreateTexture3D(&texDesc, &initData, &pTexture3D) );
    }
    else
    {
        V_RETURN( pd3dDevice->CreateTexture3D(&texDesc, 0, &pTexture3D) );
        m_data.reset();
    }
    m_pTexture = pTexture3D;

    if (bindFlag & D3D10_BIND_SHADER_RESOURCE)
    {
        D3D10_SHADER_RESOURCE_VIEW_DESC shaderResrcDesc;
        memset(&shaderResrcDesc, 0, sizeof(D3D10_SHADER_RESOURCE_VIEW_DESC));
        shaderResrcDesc.Format = texDesc.Format;
        shaderResrcDesc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE3D;
        shaderResrcDesc.Texture3D.MostDetailedMip = 0;
        shaderResrcDesc.Texture3D.MipLevels = 1;
        V_RETURN( pd3dDevice->CreateShaderResourceView(pTexture3D, &shaderResrcDesc, &m_pSRView) );
    }
    if (bindFlag & D3D10_BIND_RENDER_TARGET)
    {
        D3D10_RENDER_TARGET_VIEW_DESC rtDesc;
        memset(&rtDesc, 0, sizeof(D3D10_RENDER_TARGET_VIEW_DESC));
        rtDesc.Format = texDesc.Format;
        rtDesc.ViewDimension = D3D10_RTV_DIMENSION_TEXTURE3D;
        rtDesc.Texture3D.MipSlice = 0;
        rtDesc.Texture3D.FirstWSlice = 0;
        rtDesc.Texture3D.WSize = 0; //TODO: figure out
        V_RETURN( pd3dDevice->CreateRenderTargetView(pTexture3D, &rtDesc, &m_pRenderTargetView) );
    }

    return S_OK;
}

HRESULT CTexture3D::CopyFrom(boost::shared_ptr<CTexture> &pSrc)
{
    boost::shared_ptr<CTexture3D> pSrc3D = boost::dynamic_pointer_cast<CTexture3D>(pSrc);
    if (!pSrc3D)
    {
        W32ERR(_T("Texture dimensions don't match!"));
        return E_INVALIDARG;
    }

    HRESULT hr;
    if (pSrc3D->m_dimension[0] != m_dimension[0] || pSrc3D->m_dimension[1] != m_dimension[1] || pSrc3D->m_dimension[2] != m_dimension[2] || pSrc3D->m_format != m_format)
    {
        V_RETURN( Resize(pSrc3D->m_dimension[0], pSrc3D->m_dimension[1], pSrc3D->m_dimension[2], pSrc3D->m_format) );
    }
    CEngine::Instance().GetD3DDevice()->CopyResource(m_pTexture, pSrc3D->m_pTexture); //async
    return S_OK;
}

HRESULT CTexture3D::Resize(std::size_t dim1, std::size_t dim2, std::size_t dim3, DXGI_FORMAT format)
{
    if (dim1 == 0 || dim2 == 0 || dim3 == 0)
        return E_INVALIDARG;

    if (!m_releaseMem) //memory re-allocation not allowed
        return E_ACCESSDENIED;
    
    m_dimension[0] = dim1;
    m_dimension[1] = dim2;
    m_dimension[2] = dim3;
    m_format = format;
    texture_format::Mapping(m_format, m_bpc, m_nChannel, m_isFloat);

    if (m_bInitialized)
    {
        m_data.reset(new char[((((m_nChannel * m_bpc / 8 * dim1) + 3) >> 2) << 2) * dim2 * dim3]);
    }

    HRESULT hr;
    OnDestroy();
    V_RETURN( OnCreateDevice(CEngine::Instance().GetD3DDevice(), 0) );
    

    W32ERR(_T("Failed to resize buffer for 3D texture!"));
    return E_FAIL;	
}


void CTexture3D::OnDestroy() //virtual
{
    DestroyResource3D();
    DestroyResource();
}

void CTexture3D::DestroyResource3D()
{
    SAFE_RELEASE(m_pTexture);
}


/////////////////////////////////////////////////
// Cube Texture
CTextureCube::CTextureCube(
    UINT usage,
    TextureSpec::BindTarget bindTarget,
    std::size_t w,
    std::size_t h,
    DXGI_FORMAT format)
    : CTexture((TextureSpec::TextureUsage)usage, TextureSpec::TD_2D, bindTarget, false, format)
{
    m_dimension[0] = w;
    m_dimension[1] = h;
}

CTextureCube::CTextureCube(
    UINT usage,
    TextureSpec::BindTarget bindTarget,
    w32lib::ImagePtr imgPtr)
    : CTexture((TextureSpec::TextureUsage)usage, TextureSpec::TD_2D, bindTarget, true)
{
    m_pMemImg = imgPtr;
    m_dimension[0] = m_pMemImg->GetWidth() / 6;
    m_dimension[1] = m_pMemImg->GetHeight();
}

CTextureCube::~CTextureCube()
{
    OnDestroy();
    m_pMemImg.Delete();
}

void CTextureCube::OnDestroy() //virtual
{
    DestroyResourceCube();
    DestroyResource();
}

void CTextureCube::DestroyResourceCube()
{
    SAFE_RELEASE(m_pTexture);
}


HRESULT CTextureCube::OnCreateDevice(ID3D10Device *pd3dDevice, 
    const DXGI_SURFACE_DESC *pBufferSurfaceDesc)
{
    if (m_bInitialized && 0 == m_pMemImg.Get())
        return E_FAIL;
    HRESULT hr;

    DXGI_FORMAT format;
    //get texture format
    if (m_bInitialized)
    {
        m_dimension[0] = m_pMemImg->GetWidth() / 6;
        m_dimension[1] = m_pMemImg->GetHeight();
        format = texture_format::Mapping(m_pMemImg);
    }
    else
    {
        format = m_format;
    }

    if (0 == m_dimension[0] || 0 == m_dimension[1] || 0 == m_usage)
        return E_INVALIDARG;

    if (DXGI_FORMAT_UNKNOWN == format)
        return E_NOTIMPL;

    D3D10_USAGE usage = D3D10_USAGE_DEFAULT;
    UINT bindFlag = 0, cpuAccess = 0;
    texture_format::Mapping(usage, bindFlag, cpuAccess, m_usage, m_bindTarget);

    if (!m_bInitialized && (usage == D3D10_USAGE_IMMUTABLE)) //immutable texture must be initialized
        return E_INVALIDARG;

    ID3D10Texture2D *pTexture2D = 0;

    //set texture format
    D3D10_TEXTURE2D_DESC texDesc;
    memset( &texDesc, 0, sizeof(D3D10_TEXTURE2D_DESC) );
    texDesc.Width = m_dimension[0];
    texDesc.Height = m_dimension[1];
    texDesc.MiscFlags = D3D10_RESOURCE_MISC_TEXTURECUBE;
    texDesc.MipLevels = 1;
    texDesc.ArraySize = 6;
    texDesc.Format = format;
    texDesc.SampleDesc.Count = 1;
    texDesc.Usage = usage;
    texDesc.BindFlags = bindFlag;
    texDesc.CPUAccessFlags = cpuAccess;

    //create texture
    if (m_bInitialized)
    {
        D3D10_SUBRESOURCE_DATA initData[6];
        for (UINT face = 0; face < 6; face++)
        {
            initData[face].pSysMem = (char *)(m_pMemImg->GetBuffer()) + face * m_dimension[0] * m_pMemImg->GetBPP() / 8;
            initData[face].SysMemPitch = m_pMemImg->BytesPerLine(m_dimension[0] * 6, m_pMemImg->GetBPP() / m_pMemImg->GetBPC());
        }

        V_RETURN( pd3dDevice->CreateTexture2D(&texDesc, initData, &pTexture2D) );
    }
    else
    {
        V_RETURN( pd3dDevice->CreateTexture2D(&texDesc, 0, &pTexture2D) );

        if (usage & D3D10_USAGE_STAGING) //memory is only allocated if it's staging texture
        {
            UINT bpc, channel;
            bool isFloat;
            m_pMemImg->Delete();
            if (texture_format::Mapping(format, bpc, channel, isFloat))
            {
                if (m_pMemImg->New(w32lib::ImagePtr::Mapping(bpc, isFloat)))
                    m_pMemImg->SetSize(m_dimension[0] * 6, m_dimension[1], channel);
            }
        }
    }
    m_pTexture = pTexture2D;

    if (bindFlag & D3D10_BIND_SHADER_RESOURCE)
    {
        D3D10_SHADER_RESOURCE_VIEW_DESC shaderResrcDesc;
        memset(&shaderResrcDesc, 0, sizeof(D3D10_SHADER_RESOURCE_VIEW_DESC));
        shaderResrcDesc.Format = texDesc.Format;
        shaderResrcDesc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURECUBE;
        shaderResrcDesc.TextureCube.MostDetailedMip = 0;
        shaderResrcDesc.TextureCube.MipLevels = 1;
        V_RETURN( pd3dDevice->CreateShaderResourceView(pTexture2D, &shaderResrcDesc, &m_pSRView) );
    }
    if (bindFlag & D3D10_BIND_RENDER_TARGET)
    {
        D3D10_RENDER_TARGET_VIEW_DESC rtDesc;
        memset(&rtDesc, 0, sizeof(D3D10_RENDER_TARGET_VIEW_DESC));
        rtDesc.Format = texDesc.Format;
        rtDesc.ViewDimension = D3D10_RTV_DIMENSION_TEXTURE2DARRAY;
        rtDesc.Texture2DArray.ArraySize = 6;
        rtDesc.Texture2DArray.FirstArraySlice = 0;
        rtDesc.Texture2DArray.MipSlice = 0;
        V_RETURN( pd3dDevice->CreateRenderTargetView(pTexture2D, &rtDesc, &m_pRenderTargetView) );
    }
    if (bindFlag & D3D10_BIND_DEPTH_STENCIL)
    {
        D3D10_DEPTH_STENCIL_VIEW_DESC dsDesc;
        memset(&dsDesc, 0, sizeof(D3D10_DEPTH_STENCIL_VIEW_DESC));
        dsDesc.Format = texDesc.Format;
        dsDesc.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2DARRAY;
        dsDesc.Texture2DArray.ArraySize = 6;
        dsDesc.Texture2DArray.FirstArraySlice = 0;
        dsDesc.Texture2DArray.MipSlice = 0;
        V_RETURN( pd3dDevice->CreateDepthStencilView(pTexture2D, &dsDesc, &m_pDepthStencilView) );
    }

    return S_OK;
}

HRESULT CTextureCube::Resize(std::size_t faceWidth, std::size_t faceHeight, DXGI_FORMAT format)
{
    if (faceWidth == 0 || faceHeight == 0)
        return E_INVALIDARG;

    HRESULT hr;
    std::size_t bpc, channel;
    bool isFloat;

    if (!texture_format::Mapping(format, bpc, channel, isFloat))
    {
        W32ERR(_T("2D texture format not recognized!"));
        return E_FAIL;
    }

    if (m_pMemImg->Get())
    {
        w32lib::ImageBytePerChannel bpcenum = w32lib::ImagePtr::Mapping(bpc, isFloat);

        m_pMemImg->Delete();
        if (m_pMemImg->New(bpcenum) && m_pMemImg->SetSize(faceWidth * 6, faceHeight, channel))
        {
            OnDestroy();
            m_bInitialized = true;
            V_RETURN(OnCreateDevice(CEngine::Instance().GetD3DDevice(), 0)); //assuming OnCreateDevice() doesn't use DXGI_SURFACE_DESC
            return S_OK;
        }
    }
    else
    {
        OnDestroy();
        m_bInitialized = false;
        m_dimension[0] = faceWidth; m_dimension[1] = faceHeight;
        m_format = format;
        V_RETURN(OnCreateDevice(CEngine::Instance().GetD3DDevice(), 0)); //assuming OnCreateDevice() doesn't use DXGI_SURFACE_DESC
    }

    W32ERR(_T("Failed to resize 2D texture!"));
    return E_FAIL;
}

HRESULT CTextureCube::CopyFrom(boost::shared_ptr<CTexture> &pSrc)
{
    boost::shared_ptr<CTextureCube> pSrcCube = boost::dynamic_pointer_cast<CTextureCube>(pSrc);
    if (!pSrcCube)
    {
        W32ERR(_T("Texture dimensions don't match!"));
        return E_INVALIDARG;
    }

    HRESULT hr;
    if (pSrcCube->m_dimension[0] != m_dimension[0] 
        || pSrcCube->m_dimension[1] != m_dimension[1] 
        || pSrcCube->m_format != m_format)
    {
        V_RETURN( Resize(pSrcCube->m_dimension[0], 
            pSrcCube->m_dimension[1], 
            pSrcCube->m_format) );
    }
    CEngine::Instance().GetD3DDevice()->CopyResource(m_pTexture, pSrcCube->m_pTexture); //async
    return S_OK;
}

HRESULT CTextureCube::ReadBack()
{
    D3D10_USAGE usage = D3D10_USAGE_DEFAULT;
    UINT bindFlag = 0, cpuAccess = 0;
    texture_format::Mapping(usage, bindFlag, cpuAccess, m_usage, m_bindTarget);
    if (0 == (usage & D3D10_USAGE_STAGING)) //not staging, readback not allowed!
    {
        W32ERR(_T("Reading a non-staging texture!"));
        return E_ACCESSDENIED;
    }

    HRESULT hr;
    ID3D10Texture2D *pTex2D = static_cast<ID3D10Texture2D *>(m_pTexture);
    D3D10_MAPPED_TEXTURE2D mapped;

    for (UINT face = 0; face < 6; face++)
    {
        UINT subResrc = D3D10CalcSubresource(0, face, 1);
        V_RETURN( pTex2D->Map(subResrc, D3D10_MAP_READ, 0, &mapped) );

        char *pDst = (char *)m_pMemImg->GetBuffer() + face * m_dimension[0] * m_pMemImg->GetBPP() / 8;
        char *pSrc = (char *)mapped.pData;
        for (size_t y = 0; y < m_dimension[1]; y++)
        {
            memcpy(pDst, pSrc, mapped.RowPitch);
            pDst += m_pMemImg->BytesPerLine();
            pSrc += mapped.RowPitch;
        }
        pTex2D->Unmap(subResrc);
    }
    return S_OK;
}

}}} // namespaces